package com.bjy.qa.agent.transport.websocket;

import com.bjy.qa.agent.exception.MyException;
import com.bjy.qa.agent.model.KeyValueStore;
import com.bjy.qa.agent.tools.SpringTool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.ByteString;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class WebSocketHelper {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketHelper.class);

    private WebSocket webSocket;

    private String charset = "UTF-8"; // 默认参数字符集
    private int timeout = 30; // 默认超时时间
    private EndingType endingType = EndingType.SINGLE_PACK; // 默认为 单包报文

    private static final WebSocketConf webSocketConf; // websocket 配置信息

    static {
        webSocketConf = SpringTool.getBean(WebSocketConf.class);
        logger.info("WebSocket 配置信息：{}", webSocketConf);
    }

    /**
     * 获取实例（url 不带参数）
     * @param url websocket 地址
     * @param webSocketListener websocket 请求回调类
     * @return 实例
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public WebSocketHelper getInstance(EndingType endingType, String url, WebSocketListener webSocketListener) throws UnsupportedEncodingException {
        this.endingType = endingType;
        return getInstance(endingType, url, null, webSocketListener);
    }

    /**
     * 获取实例（url 带参数）
     * @param url websocket 地址
     * @param urlParams url 参数
     * @param webSocketListener websocket 请求回调类
     * @return 实例
     * @throws UnsupportedEncodingException UnsupportedEncodingException
     */
    public WebSocketHelper getInstance(EndingType endingType, String url, List<KeyValueStore> urlParams, WebSocketListener webSocketListener) throws UnsupportedEncodingException {
        String fullUrl = getHttpGetURL(url, urlParams);

        Request request = new Request.Builder().get().url(fullUrl).build();

        webSocket = getOkHttpClient().newWebSocket(request, new okhttp3.WebSocketListener() {
            @Override
            public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                super.onClosed(webSocket, code, reason);
                if (webSocketConf.getDebug()) {
                    logger.info("Websocket onClosed 回调。");
                }
                webSocketListener.onClosed(WebSocketHelper.this, code, reason);
            }

            @Override
            public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                super.onClosing(webSocket, code, reason);
                if (webSocketConf.getDebug()) {
                    logger.info("Websocket onClosing 回调。");
                }
                webSocketListener.onClosing(WebSocketHelper.this, code, reason);
            }

            @Override
            public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
                super.onFailure(webSocket, t, response);
                if (webSocketConf.getDebug()) {
                    logger.info("Websocket onFailure 回调。Throwable : " + t + "。 Response: " + response);
                }
                webSocketListener.onFailure(WebSocketHelper.this, t, response);
            }

            @Override
            public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
                super.onMessage(webSocket, text);
                if (webSocketConf.getDebug()) {
                    logger.info("<-- onMessage 回调。text : " + text);
                }
                if (endingType == EndingType.SINGLE_PACK) {
                    webSocketListener.onMessage(WebSocketHelper.this, text);
                } else {
                    throw new MyException("目前只支持单包报文（EndingType.SINGLE_PACK），其它类型报文暂未实现");
                }
            }

            @Override
            public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
                super.onMessage(webSocket, bytes);
                if (webSocketConf.getDebug()) {
                    logger.info("<-- onMessage 回调。bytes : " + bytes);
                }
                if (endingType == EndingType.SINGLE_PACK) {
                    webSocketListener.onMessage(WebSocketHelper.this, bytes);
                } else {
                    throw new MyException("目前只支持单包报文（EndingType.SINGLE_PACK），其它类型报文暂未实现");
                }
            }

            @Override
            public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
                super.onOpen(webSocket, response);
                if (webSocketConf.getDebug()) {
                    logger.info("Websocket onOpen 回调。" + response);
                }
                webSocketListener.onOpen(WebSocketHelper.this, response);
             }
        });

        return this;
    }

    /**
     * 生成 get 方法的请求 url
     * 例如：urr=http://www.baidu.com/aaa parameters=[param1=value1, param2=value2] 生成的 url 为：http://www.baidu.com/aaa?param1=value1&param2=value2
     * @param url 请求 url
     * @param parameters 请求参数
     * @return 生成的 url
     */
    private String getHttpGetURL(String url, List<KeyValueStore> parameters) {
        if ((parameters == null) || (StringUtils.isEmpty(url))) {
            return url;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(url);
        if (!StringUtils.endsWith(url, "?")) {
            sb.append("?");
        }
        for (KeyValueStore kvs : parameters) {
            String name = kvs.getName();
            String value = encode((String) kvs.getValue());
            sb.append(name).append("=").append(value).append("&");
        }
        String result = sb.toString();
        if (result.endsWith("&")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    /**
     * 对传入的内容进行 url 编码
     * @param value 需要编码的内容
     * @return 编码后的内容
     */
    private String encode(String value) {
        try {
            if (value != null)
                return URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 设置默认超时时间
     * @param second 超时时间，秒
     */
    public void setTimeout(int second) {
        timeout = second;
    }

    /**
     * 返回默认超时时间
     * @return
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * 设置字符集
     * @param charset 字符集
     */
    public void setCharset(String charset) {
        this.charset = charset;
    }

    /**
     * 返回字符集
     * @return 字符集
     */
    public String getCharset() {
        return this.charset;
    }

    /**
     * websocket 请求回调类
     * @param <T>
     */
    public interface WebSocketListener<T> {
        /**
         * Invoked when a web socket has been accepted by the remote peer and may begin transmitting
         * messages.
         */
        public void onOpen(WebSocketHelper webSocketHelper, Response response);

        /** Invoked when a text (type `0x1`) message has been received. */
        public void onMessage(WebSocketHelper webSocketHelper, String text);

        /** Invoked when a binary (type `0x2`) message has been received. */
        public void onMessage(WebSocketHelper webSocketHelper, ByteString bytes);

        /**
         * Invoked when the remote peer has indicated that no more incoming messages will be transmitted.
         */
        public void onClosing(WebSocketHelper webSocketHelper, int code, String reason);

        /**
         * Invoked when both peers have indicated that no more messages will be transmitted and the
         * connection has been successfully released. No further calls to this listener will be made.
         */
        public void onClosed(WebSocketHelper webSocketHelper, int code, String reason);

        /**
         * Invoked when a web socket has been closed due to an error reading from or writing to the
         * network. Both outgoing and incoming messages may have been lost. No further calls to this
         * listener will be made.
         */

        /**
         * 由于从网络读取或写入错误而关闭web套接字时调用。传出消息和传入消息都可能已丢失。不会再调用此侦听器。
         * @param webSocketHelper
         * @param throwable
         * @param response
         */
        public void onFailure(WebSocketHelper webSocketHelper, Throwable throwable, Response response);
    }


    /**
     * 得到 OkHttpClient 对象
     * @return OkHttpClient
     */
    private OkHttpClient getOkHttpClient() {
        HttpLoggingInterceptor loggingInterceptor = null; // Log信息拦截器

        if (webSocketConf.getDebug()) {
            loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(String message) {
                    logger.info(message);
                }
            });
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //设置日志打印级别（所有日志，包括 request、response、header 和 body）
        }

        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
                .readTimeout(timeout, TimeUnit.SECONDS)
                .writeTimeout(timeout, TimeUnit.SECONDS)
                .connectTimeout(timeout, TimeUnit.SECONDS);
        if (loggingInterceptor != null) {
            clientBuilder.addInterceptor(loggingInterceptor);
        }
        return clientBuilder.build();
    }

    /**
     * 发送 UTF-8 编码的消息
     * @param text 要发送的消息内容
     * @return 如果已经将消息放入队列返回true，如果超过缓冲区大小或websocket已关闭则返回false
     */
    public boolean send(String text) {
        if (webSocket == null) {
            return false;
        } else {
            if (webSocketConf.getDebug()) {
                logger.info("--> send。text : " + text);
            }
            return webSocket.send(text);
        }
    }

    /**
     * 尝试关闭此 websocket。在关闭前，将发送已排队的消息，但之后的 [send] 将返回 false
     * @return 关闭成功返回true，关闭失败返回false
     */
    public boolean close(String msg) {
        if (webSocketConf.getDebug()) {
            logger.info(msg);
        }
        return webSocket.close(1000, "Close this web socket!");
    }

    /**
     * 立即释放此 websocket 所持有的资源，并丢弃任何排队的消息
     */
    public void cancel() {
        webSocket.cancel();
    }
}
